﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.ComponentModel;
    using System.Data.Linq;
    using System.Data.Linq.Mapping;
    using System.Diagnostics.CodeAnalysis;
    using Microsoft.Phone.Data.Linq.Mapping;

    /// <content>Contains the LINQ to SQL portion of the <see cref="InboundArticle"/> class.</content>
    [Table]
    [Index(Columns = "GroupId,Id DESC")]
    [Index(Columns = "GroupId,ReceiveContent,Number")]
    [Index(Columns = "GroupId,IsBeingWatched,HasBeenRead")]
    [Index(Columns = "GroupId,HasBeenRead")]
    [Index(Columns = "GroupId,MessageId", IsUnique = true)]
    [Index(Columns = "ReferencedId,Id")]
    [SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "The order of the properties defines the order of database columns.")]
    internal sealed partial class InboundArticle : INotifyPropertyChanging
    {
        private int id;
        private int groupId;
        private int? referencedId;
        private int? originalId;
        private long number;
        private DateTime? dateUtc;
        private string from;
        private string subject;
        private long? articleOffset;
        private long? articleLength;
        private bool receiveContent;
        private bool? hasAttachments;
        private bool isBeingWatched;
        private bool isExpanded;
        private bool hasBeenRead;
        private Link<string> messageId;

        private EntityRef<Group> groupRef;
        private EntityRef<InboundArticle> referencedArticle;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        [Column(IsPrimaryKey = true)]
        internal int Id 
        {
            get { return this.id; }
            private set { this.SetValue("Id", ref this.id, value); }
        }

        [Column]
        internal int GroupId
        {
            get { return this.groupId; }
            private set { this.SetValue("GroupId", ref this.groupId, value); }
        }

        [Column]
        internal int? ReferencedId
        {
            get { return this.referencedId; }
            private set { this.SetValue("ReferencedId", ref this.referencedId, value); }
        }

        // We're deliberately only setting the OriginalId for newly inserted articles but never adjust it when
        // the original article is deleted (due to automatic deletion of old articles). The rationale for this
        // is as follows:
        // 1. OriginalId is used in exactly one query, namely the one that determines which original articles
        // are either themselves watched and/or unread or have direct or indirect followups which are watched
        // and/or unread. This particular query will simply no longer return threads where OriginalId
        // refers to an article that is no longer in existence, which is the desired outcome.
        // 2. It would be rather costly to maintain OriginalId when the original article is deleted. For each
        // deleted original article, all its direct and indirect followups would need updating. This in turn
        // would make followup articles look exactly like original articles, which would again require more
        // costly updating when these articles are deleted later.
        //
        // It follows that OriginalId cannot be made a proper foreign key. Moreover, it can neither be the
        // backing field for a proper association.
        [Column]
        internal int? OriginalId
        {
            get { return this.originalId; }
            private set { this.SetValue("OriginalId", ref this.originalId, value); }
        }

        /// <summary>See <see cref="IInboundArticle.Number"/>.</summary>
        [Column]
        public long Number
        {
            get { return this.number; }
            internal set { this.SetValue("Number", ref this.number, value); }
        }

        /// <summary>See <see cref="IInboundArticle.DateUtc"/>.</summary>
        [Column]
        public DateTime? DateUtc
        {
            get { return this.dateUtc; }
            internal set { this.SetValue("DateUtc", ref this.dateUtc, value); }
        }

        /// <summary>See <see cref="IInboundArticle.From"/>.</summary>
        [Column(DbType = "NVARCHAR(128)")]
        public string From
        {
            get { return this.from; }
            internal set { this.SetValue("From", ref this.from, value); }
        }

        /// <summary>See <see cref="IInboundArticle.Subject"/>.</summary>
        [Column(DbType = "NVARCHAR(128)")]
        public string Subject
        {
            get { return this.subject; }
            internal set { this.SetValue("Subject", ref this.subject, value); }
        }

        [Column]
        private long? ArticleOffset
        {
            get { return this.articleOffset; }
            set { this.SetValue("ArticleOffset", ref this.articleOffset, value); }
        }

        [Column]
        private long? ArticleLength
        {
            get { return this.articleLength; }
            set { this.SetValue("ArticleLength", ref this.articleLength, value); }
        }

        [Column]
        internal bool ReceiveContent
        {
            get { return this.receiveContent; }
            set { this.SetValue("ReceiveContent", ref this.receiveContent, value); }
        }

        /// <summary>See <see cref="IInboundArticle.HasAttachments"/>.</summary>
        [Column(Storage = "hasAttachments")]
        public bool? HasAttachments
        {
            get
            {
                return this.hasAttachments;
            }

            set
            {
                this.AssertValid();
                this.SetValue("HasAttachments", ref this.hasAttachments, value);
            }
        }

        /// <summary>See <see cref="IInboundArticle.IsBeingWatched"/>.</summary>
        [Column(Storage = "isBeingWatched")]
        public bool IsBeingWatched
        {
            get
            {
                return this.isBeingWatched;
            }

            set
            {
                this.AssertValid();
                this.SetValue("IsBeingWatched", ref this.isBeingWatched, value);
                this.ReceiveContent = !this.HasFullArticleBeenReceived && value;
            }
        } 

        /// <summary>See <see cref="IInboundArticle.IsExpanded"/>.</summary>
        [Column(Storage = "isExpanded")]
        public bool IsExpanded
        {
            get
            {
                return this.isExpanded;
            }

            set
            {
                this.AssertValid();
                this.SetValue("IsExpanded", ref this.isExpanded, value);
            }
        } 

        /// <summary>See <see cref="IInboundArticle.HasBeenRead"/>.</summary>
        [Column(Storage = "hasBeenRead")]
        public bool HasBeenRead
        {
            get
            {
                return this.hasBeenRead;
            }

            set
            {
                this.AssertValid();
                this.SetValue("HasBeenRead", ref this.hasBeenRead, value);
            }
        }

        // Maximum length taken from http://www.ietf.org/rfc/rfc3977.txt, 3.6. 
        [Column(Storage = "messageId", CanBeNull = false, DbType = "NVARCHAR(250) NOT NULL")]
        public string MessageId
        {
            get
            {
                return this.messageId.Value;
            }

            set
            {
                if (value != this.messageId.Value)
                {
                    this.NotifyPropertyChanging("MessageId");
                    this.messageId.Value = value;
                }
            }
        }

        // This dramatically accelerates updates (~4x) on the phone. On the desktop, performance is actually a bit
        // worse because this column will trigger optimistic concurrency checks that would otherwise not be necessary
        // (before this column was added, all other columns were marked with UpdateCheck = UpdateCheck.Never).
        // http://jesseliberty.com/2011/05/14/best-practices-for-local-databases/
        [Column(IsVersion = true)]
        internal Binary Version { get; set; }

        /// <summary>See <see cref="INotifyPropertyChanging.PropertyChanging"/>.</summary>
        public event PropertyChangingEventHandler PropertyChanging;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        [Association(Name = "Group_InboundArticle", Storage = "groupRef", ThisKey = "GroupId", IsForeignKey = true)]
        internal Group Group
        {
            get { return this.groupRef.Entity; }
        }

        [Association(Name = "InboundArticle_InboundArticle", Storage = "referencedArticle", ThisKey = "ReferencedId", IsForeignKey = true)]
        internal InboundArticle ReferencedArticle
        {
            get { return this.referencedArticle.Entity; }
        }

        internal void MakeOriginal()
        {
            if (this.referencedArticle.Entity != null)
            {
                this.NotifyPropertyChanging("ReferencedArticle");
                this.referencedArticle.Entity = null;
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void SetValue<T>(string propertyName, ref T currentValue, T newValue)
        {
            if (!object.Equals(currentValue, newValue))
            {
                this.NotifyPropertyChanging(propertyName);
                currentValue = newValue;
            }
        }

        private void NotifyPropertyChanging(string propertyName)
        {
            var handler = this.PropertyChanging;

            if (handler != null)
            {
                handler(this, new PropertyChangingEventArgs(propertyName));
            }
        }
    }
}
